ANDROID COMPONENTS

BROADCAST RECEIVER EXPLAINED

The invisible messenger of Android. Intercept system-wide events, respond to app signals, and build reactive applications with one of Android's most powerful components.

android.content.BroadcastReceiver Intent-based Event-driven
SCROLL

What is a
Broadcast Receiver?

A Broadcast Receiver is an Android component that allows your application to subscribe to system-wide or app-level events. Think of it as a radio tuner — it listens on specific channels and reacts when a signal arrives.

When something significant happens — the battery gets low, Wi-Fi connects, an SMS arrives, or even when your own app wants to signal other parts of itself — Android sends out a broadcast Intent. Any registered receiver tuned to that frequency wakes up and responds.

Receivers work even when your app isn't in the foreground, making them ideal for background reactions to real-world events — without keeping your app alive unnecessarily.

SIGNAL FLOW
📱 App / System
⚡ Intent
🤖 Android OS
📡 Your Receiver
broadcast signal propagating →

How does it work?

01

An event occurs

Something triggers a broadcast — the system boots, a network changes, a call comes in, or your app fires a custom event. This is the spark that starts the chain.

TRIGGER
02

An Intent is packaged

The broadcaster creates an Intent object with an action string (like ACTION_BATTERY_LOW) and optional extras — data that travels with the signal.

INTENT CREATION
03

Android routes the broadcast

The OS checks all registered receivers — both those declared in AndroidManifest.xml and those registered dynamically — to find matching IntentFilters.

ROUTING
04

onReceive() is called

Your receiver's onReceive(Context, Intent) method fires on the main thread. You have a strict 10-second window to complete work — for longer tasks, delegate to a Service or WorkManager.

EXECUTION

Types of Broadcasts

🌐

Normal Broadcast

Delivered to all receivers asynchronously and in an undefined order. Most efficient type — all receivers get the broadcast roughly simultaneously with no defined priority.

sendBroadcast()
📋

Ordered Broadcast

Delivered to receivers one at a time based on priority. Each receiver can propagate or abort the broadcast before the next receiver sees it. Useful for interception patterns.

sendOrderedBroadcast()
🔒

Local Broadcast

Stays within your app's process using LocalBroadcastManager. More efficient and secure — data never leaves your app. Ideal for in-app communication.

LocalBroadcastManager
📌

Sticky Broadcast (Deprecated in API 21)

Broadcasts that stay in the system after being sent. When a new receiver registered, it immediately receives the last sticky broadcast for that action. Replaced by explicit data stores like SharedPreferences or ViewModel.

sendStickyBroadcast()

Code Examples

// Step 1: Create your BroadcastReceiver class
class BatteryReceiver : BroadcastReceiver() {

    @Override
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            Intent.ACTION_BATTERY_LOW -> {
                // Battery is critically low — take action
                showLowBatteryNotification(context)
            }
            Intent.ACTION_BATTERY_OKAY -> {
                // Battery recovered — clear alerts
                clearNotification(context)
            }
        }
    }

    private fun showLowBatteryNotification(context: Context) {
        // Build and show notification...
        val msg = "⚡ Battery is critically low!"
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show()
    }
}
<!-- AndroidManifest.xml — Static Registration -->
<manifest>

    <application>

        <!-- Declare the receiver component -->
        <receiver
            android:name=".BatteryReceiver"
            android:exported="false">

            <intent-filter>
                <!-- Listen for battery events -->
                <action android:name="android.intent.action.BATTERY_LOW" />
                <action android:name="android.intent.action.BATTERY_OKAY" />
            </intent-filter>

        </receiver>

    </application>

</manifest>

<!-- Note: Many implicit broadcasts can NO LONGER be registered  -->
<!-- in the manifest as of Android 8.0 (API 26) — use Context     -->
<!-- registration or WorkManager for background tasks instead.    -->
class MainActivity : AppCompatActivity() {

    private lateinit var networkReceiver: NetworkChangeReceiver
    private lateinit var filter: IntentFilter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Create receiver and intent filter
        networkReceiver = NetworkChangeReceiver()
        filter = IntentFilter().apply {
            addAction(ConnectivityManager.CONNECTIVITY_ACTION)
        }
    }

    override fun onResume() {
        super.onResume()
        // ✅ Register when active
        registerReceiver(networkReceiver, filter)
    }

    override fun onPause() {
        super.onPause()
        // ✅ Always unregister to prevent memory leaks!
        unregisterReceiver(networkReceiver)
    }
}

Common System Broadcasts

🔋

Battery Low

ACTION_BATTERY_LOW

Fired when device battery drops to a critically low level. Useful for pausing sync operations.

📶

Connectivity Changed

CONNECTIVITY_ACTION

Network state changed — Wi-Fi connected, disconnected, or mobile data toggled.

🚀

Boot Completed

ACTION_BOOT_COMPLETED

Device has finished booting. Start persistent services or schedule jobs after reboot.

Time Changed

ACTION_TIME_CHANGED

User manually changed device time or date. Useful for calendar and scheduling apps.

📦

Package Installed

ACTION_PACKAGE_ADDED

A new app was installed on the device. Used by launchers and app managers.

🔌

Power Connected

ACTION_POWER_CONNECTED

Device connected to a charger. Ideal trigger for heavy background sync operations.

Receiver Lifecycle

Unlike Activities, a Broadcast Receiver has an extremely simple lifecycle — it exists only for the duration of onReceive().

📡

Broadcast Sent

System or app fires the Intent

🔍

Intent Matched

OS finds registered receivers with matching filter

onReceive()

Your code runs — max 10 seconds on main thread

💤

Receiver Idle

Method returns, receiver is inactive again

⚠️
Important: Do not perform long-running operations inside onReceive(). The system can kill your process once the method returns. For async work, use WorkManager, JobScheduler, or start a foreground Service.

Do's & Don'ts

Always unregister dynamic receivers

Unregister in onPause() or onDestroy() to prevent memory leaks and ghost receivers that silently drain resources.

Use LocalBroadcastManager for in-app signals

When communicating within your app, local broadcasts are faster, safer, and data stays within your process. Prefer it over global broadcasts.

🚫

Don't do heavy work in onReceive()

Network calls, database writes, and file I/O have no place in a receiver. Delegate to WorkManager or a Service immediately and return fast.

🚫

Don't rely on implicit manifest receivers on API 26+

Android 8.0+ restricts background implicit broadcasts. Many system actions can no longer wake static receivers — use dynamic registration or WorkManager constraints.

Set android:exported appropriately

If your receiver isn't meant to receive broadcasts from other apps, set android:exported="false" to prevent unintended external access.

Use permissions for sensitive broadcasts

Protect your custom broadcasts with a permission string to ensure only authorized apps can send or receive your proprietary signals.

Internal Working

Under the hood, Android's broadcast system is a sophisticated IPC (Inter-Process Communication) pipeline managed entirely by the ActivityManagerService (AMS). Here's what really happens.

1
sendBroadcast()
2
Binder IPC
3
AMS Queue
4
Intent Filter Match
5
Process Spawn
6
onReceive()
APP PROCESS (Sender)
Context.sendBroadcast(intent)
ActivityManager.getService()
Binder Kernel Driver
Cross-process call via /dev/binder
SYSTEM SERVER PROCESS
ActivityManagerService (AMS)
broadcastQueueLocked()
BroadcastQueue
IntentResolver
Binder Kernel Driver
Schedule receiver dispatch
APP PROCESS (Receiver)
ApplicationThread.scheduleReceiver()
ActivityThread → Handler → onReceive()

Foreground Queue

Timeout: 10 seconds

Handles broadcasts sent with FLAG_RECEIVER_FOREGROUND. Processed at high priority. Used for time-critical signals like incoming calls or SMS. Runs before background queue.

📩 SMS_RECEIVED
📞 PHONE_STATE
...
🌙

Background Queue

Timeout: 60 seconds

Handles all other broadcasts. Lower priority, processed after foreground queue drains. Most system broadcasts like BOOT_COMPLETED and BATTERY_LOW land here.

🔋 BATTERY_LOW
🚀 BOOT_COMPLETED
...
🎯

Action Match

AMS checks if the intent's action string exactly matches any declared <action> in registered IntentFilters. No action in filter = matches all.

🗂️

Category Match

Every category in the Intent must be present in the filter. Missing categories = no match. Implicit intents always include CATEGORY_DEFAULT.

🔗

Data Match

If the intent carries a URI or MIME type, it must match the filter's <data> element. Scheme, host, path, and MIME all evaluated separately.

Receiver Selected

All matching receivers are collected. For ordered broadcasts, priority score determines dispatch order. For normal broadcasts, all fire concurrently.

Your App Process
Main Thread (UI Thread)
scheduleReceiver() ← Binder
Handler.handleMessage()
BroadcastReceiver.onReceive()
⚠ MAX 10s — then ANR!
Worker Thread (goAsync)
PendingResult.goAsync()
Heavy work off main thread
PendingResult.finish()
goAsync() extends the window beyond 10s by calling PendingResult.goAsync() in onReceive(), allowing work on a background thread. Must call finish() when done or the system will ANR.
🛡️

Permission Enforcement

AMS checks android:permission on the receiver declaration. If set, only callers holding that permission can deliver broadcasts to it. Enforced in kernel via UID checks.

🔐

Signature-level Permissions

Using protectionLevel="signature" restricts broadcasts to apps signed with the same certificate. Used by system components and companion apps.

🏠

android:exported Isolation

When exported="false", AMS rejects any broadcast delivery attempt from outside the app's UID — enforced before even reaching your onReceive().

📦

Package Visibility (API 30+)

Android 11 introduced package visibility restrictions. Apps must declare <queries> in manifest to discover or interact with other apps' receivers.